/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "cg_local.h"


odMemory_t *		odMemory = NULL;
odFileSystem_t *	odFileSystem = NULL;
odCmdSystem_t *		odCmdSystem = NULL;
odCVarSystem_t *	odCVarSystem = NULL;
odParser_t *		odParser = NULL;
odSystem_t *		odSystem = NULL;
odCollision_t *		odCollision = NULL;
odRenderer_t *		odRenderer = NULL;
odSound_t *			odSound = NULL;
odEditor_t *		odEditor = NULL;

cg_t				cg;
cgs_t				cgs;
cgExport_t			cge;
cgImport_t *		cgi;

cvar_t *			cg_footSteps;
cvar_t *			cg_noRender;
cvar_t *			cg_showMiss;
cvar_t *			cg_showDeltaEntities;
cvar_t *			cg_showViewPos;
cvar_t *			cg_showMaterial;
cvar_t *			cg_viewBlend;
cvar_t *			cg_particles;
cvar_t *			cg_muzzleFlashes;
cvar_t *			cg_decals;
cvar_t *			cg_ejectBrass;
cvar_t *			cg_blood;
cvar_t *			cg_shells;
cvar_t *			cg_drawPlayerShadow;
cvar_t *			cg_testGunX;
cvar_t *			cg_testGunY;
cvar_t *			cg_testGunZ;
cvar_t *			cg_testModelRotate;
cvar_t *			cg_crosshairX;
cvar_t *			cg_crosshairY;
cvar_t *			cg_crosshairSize;
cvar_t *			cg_crosshairColor;
cvar_t *			cg_crosshairAlpha;
cvar_t *			cg_crosshairHealth;
cvar_t *			cg_crosshairNames;
cvar_t *			cg_draw2D;
cvar_t *			cg_drawCrosshair;
cvar_t *			cg_drawLagometer;
cvar_t *			cg_drawDisconnected;
cvar_t *			cg_spectator;
cvar_t *			cg_autoSwitch;
cvar_t *			cg_autoReload;
cvar_t *			cg_drawGun;
cvar_t *			cg_fov;
cvar_t *			cg_predict;


/*
 ==================
 CG_UpdateLoading
 ==================
*/
static void CG_UpdateLoading (const char *string){

	odSystem->Printf("Loading %s...\n", string);

	cgi->UpdateLoading();
}

/*
 ==================
 CG_RegisterMap
 ==================
*/
static void CG_RegisterMap (void){

	char	name[MAX_OSPATH];

	// Load the clip map
	//TODO: fix config strings
	cgs.configStrings[CS_LEVEL_NAME]=(const char*)odMemory->Alloc(MAX_OSPATH,TAG_CLIENT);
	Str_Copy((char*)cgs.configStrings[CS_LEVEL_NAME],"maps/test",MAX_OSPATH);
	Str_Copy(name, cgs.configStrings[CS_LEVEL_NAME], sizeof(name));
	Str_DefaultFileExtension(name, sizeof(name), ".bsp");

	CG_UpdateLoading("clip map");

	odCollision->LoadMap(name);

	CG_UpdateLoading("world map");

	odRenderer->LoadMap(name);

	// Load the reverbs
	Str_Copy(name, cgs.configStrings[CS_LEVEL_NAME], sizeof(name));
	Str_DefaultFileExtension(name, sizeof(name), ".reverb");

	CG_UpdateLoading("reverbs");

	odSound->LoadReverbs(name);
}

/*
 ==================
 CG_RegisterModels
 ==================
*/
static void CG_RegisterModels (void){

	int		i;

	// Load the local models
	CG_UpdateLoading("models");

	// Load the models specified by the server
	CG_UpdateLoading("game models");

	for (i = 1; i < MAX_GAME_MODELS; i++){
		if (!cgs.configStrings[CS_MODELS+i])
			break;

		cgs.gameModels[i] = odRenderer->RegisterModel(cgs.configStrings[CS_MODELS+i]);
	}

	// Load the local clip models
	CG_UpdateLoading("clip models");

	// Load the clip models specified by the server
	CG_UpdateLoading("game clip models");

	for (i = 1; i < MAX_GAME_CLIP_MODELS; i++){
		if (!cgs.configStrings[CS_CLIP_MODELS+i])
			break;

		cgs.gameClipModels[i] = odCollision->RegisterModel(cgs.configStrings[CS_CLIP_MODELS+i]);
	}
}

/*
 ==================
 CG_RegisterMaterials
 ==================
*/
static void CG_RegisterMaterials (void){

	int		i;

	// Load the local materials
	CG_UpdateLoading("materials");

	cgs.media.whiteMaterial = odRenderer->RegisterMaterialNoMip("white");
	cgs.media.charsetMaterial = odRenderer->RegisterMaterialNoMip("charset");

	// Load the materials specified by the server
	CG_UpdateLoading("game materials");

	for (i = 1; i < MAX_GAME_MATERIALS; i++){
		if (!cgs.configStrings[CS_MATERIALS+i])
			break;

		// FIXME: this is stupid (use separate CS indices?)
		if (!Str_ICompareChars(cgs.configStrings[CS_MATERIALS+i], "light ", 6))
			cgs.gameMaterials[i] = odRenderer->RegisterMaterialLight(cgs.configStrings[CS_MATERIALS+i] + 6);
		else if (!Str_ICompareChars(cgs.configStrings[CS_MATERIALS+i], "noMip ", 6))
			cgs.gameMaterials[i] = odRenderer->RegisterMaterialNoMip(cgs.configStrings[CS_MATERIALS+i] + 6);
		else
			cgs.gameMaterials[i] = odRenderer->RegisterMaterial(cgs.configStrings[CS_MATERIALS+i]);
	}
}

/*
 ==================
 CG_RegisterSoundShaders
 ==================
*/
static void CG_RegisterSoundShaders (void){

	int		i;

	// Load the local sound shaders
	CG_UpdateLoading("sound shaders");

	// Load the sound shaders specified by the server
	CG_UpdateLoading("game sound shaders");

	for (i = 1; i < MAX_GAME_SOUND_SHADERS; i++){
		if (!cgs.configStrings[CS_SOUND_SHADERS+i])
			break;

		cgs.gameSoundShaders[i] = odSound->RegisterSoundShader(cgs.configStrings[CS_SOUND_SHADERS+i]);
	}
}

/*
 ==================
 CG_ConfigStringModified
 ==================
*/
static void CG_ConfigStringModified (int index){

	if (index >= CS_MODELS && index < CS_MODELS + MAX_GAME_MODELS){
		cgs.gameModels[index - CS_MODELS] = odRenderer->RegisterModel(cgs.configStrings[index]);
		return;
	}

	if (index >= CS_CLIP_MODELS && index < CS_CLIP_MODELS + MAX_GAME_MODELS){
		cgs.gameClipModels[index - CS_CLIP_MODELS] = odCollision->RegisterModel(cgs.configStrings[index]);
		return;
	}

	if (index >= CS_MATERIALS && index < CS_MATERIALS + MAX_GAME_MATERIALS){
		// FIXME: this is stupid (use separate CS indices?)
		if (!Str_ICompareChars(cgs.configStrings[index], "light ", 6))
			cgs.gameMaterials[index - CS_MATERIALS] = odRenderer->RegisterMaterialLight(cgs.configStrings[index] + 6);
		else if (!Str_ICompareChars(cgs.configStrings[index], "noMip ", 6))
			cgs.gameMaterials[index - CS_MATERIALS] = odRenderer->RegisterMaterialNoMip(cgs.configStrings[index] + 6);
		else
			cgs.gameMaterials[index - CS_MATERIALS] = odRenderer->RegisterMaterial(cgs.configStrings[index]);

		return;
	}

	if (index >= CS_SOUND_SHADERS && index < CS_SOUND_SHADERS + MAX_GAME_SOUND_SHADERS){
		cgs.gameSoundShaders[index - CS_SOUND_SHADERS] = odSound->RegisterSoundShader(cgs.configStrings[index]);
		return;
	}
}


/*
 ==============================================================================

 EXPORTED FUNCTIONS

 ==============================================================================
*/


/*
 ==================
 CG_WriteGameState
 ==================
*/
static int CG_WriteGameState (int offset, ByteBuffer *msg){

	int		startSize, endSize;
	int		index, count = 0;

	if (offset >= GS_START_CONFIG_STRINGS && offset <= GS_END_CONFIG_STRINGS){
		// Leave space for header
		startSize = msg->getBitsWritten();

		msg->WriteLong(0);
		msg->WriteLong(0);
		msg->WriteLong(0);

		// Write as many config strings as possible
		index = offset - GS_START_CONFIG_STRINGS;

		while (index < MAX_CONFIG_STRINGS){
			if (!cgs.configStrings[index]){
				index++;
				continue;
			}

			if (msg->getBytesWritten() + Str_Length(cgs.configStrings[index]) + 5 >= msg->getSize())
				break;

			msg->WriteShort(index);
			msg->WriteString(cgs.configStrings[index]);

			count++;
			index++;
		}

		if (index != MAX_CONFIG_STRINGS)
			index = GS_START_CONFIG_STRINGS + index;
		else
			index = GS_START_BASELINES;

		// Write the header
		endSize = msg->getBitsWritten();

		msg->SetWriteState(startSize);

		msg->WriteLong(offset);	// Start offset
		msg->WriteLong(count);		// Number of config strings
		msg->WriteLong(index);		// Next offset

		msg->SetWriteState(endSize);

		return index;
	}
	// Invalid game state request
	msg->WriteLong(-1);
	msg->WriteLong(-1);
	msg->WriteLong(-1);

	return -1;
}

/*
 ==================
 CG_ParseGameState
 ==================
*/
static int CG_ParseGameState (ByteBuffer *msg){

	char	string[MAX_STRING_LENGTH];
	int		offset, count, index;

	offset = msg->ReadLong();
	count = msg->ReadLong();
	index = msg->ReadLong();

	if (offset >= GS_START_CONFIG_STRINGS && offset <= GS_END_CONFIG_STRINGS){
		offset = index;

		while (count){
			index = msg->ReadShort();
			msg->ReadString(string, sizeof(string));

			if (cgs.configStrings[index])
				odMemory->Free(cgs.configStrings[index]);

			cgs.configStrings[index] = odMemory->CopyString(string, TAG_CLIENT);

			count--;
		}

		return offset;
	}

	if (offset >= GS_START_BASELINES && offset <= GS_END_BASELINES){
		offset = index;

		while (count){
			index = msg->ReadBits(ENTITYNUM_BITS);


			entityState_t	*state, nullState;

			memset(&nullState, 0, sizeof(entityState_t));

			state = &cgs.baselines[index];
			state->number = index;

			state->type = (entityType_t)msg->ReadByte();

			if (state->type == ET_LIGHT){
				state->origin.x = msg->ReadDeltaFloat(nullState.origin.x);
				state->origin.y = msg->ReadDeltaFloat(nullState.origin.y);
				state->origin.z = msg->ReadDeltaFloat(nullState.origin.z);
				state->center.x = msg->ReadDeltaFloat(nullState.center.x);
				state->center.y = msg->ReadDeltaFloat(nullState.center.y);
				state->center.z = msg->ReadDeltaFloat(nullState.center.z);
				state->angles.pitch = msg->ReadDeltaAngle16(nullState.angles.pitch);
				state->angles.yaw = msg->ReadDeltaAngle16(nullState.angles.yaw);
				state->angles.roll = msg->ReadDeltaAngle16(nullState.angles.roll);
				state->radius.x = msg->ReadDeltaFloat(nullState.radius.x);
				state->radius.y = msg->ReadDeltaFloat(nullState.radius.y);
				state->radius.z = msg->ReadDeltaFloat(nullState.radius.z);
				state->fovX = msg->ReadDeltaFloat(nullState.fovX);
				state->fovY = msg->ReadDeltaFloat(nullState.fovY);
				state->falloffRange = msg->ReadDeltaFloat(nullState.falloffRange);
				state->projected = msg->ReadBool();
				state->parallel = msg->ReadBool();
				state->noShadows = msg->ReadBool();
				state->detailLevel = msg->ReadDeltaLong(nullState.detailLevel);
				state->materialIndex = msg->ReadDeltaLong(nullState.materialIndex);
				state->materialParms[0] = msg->ReadDeltaFloat(nullState.materialParms[0]);
				state->materialParms[1] = msg->ReadDeltaFloat(nullState.materialParms[1]);
				state->materialParms[2] = msg->ReadDeltaFloat(nullState.materialParms[2]);
				state->materialParms[3] = msg->ReadDeltaFloat(nullState.materialParms[3]);
				state->materialParms[4] = msg->ReadDeltaFloat(nullState.materialParms[4]);
				state->materialParms[5] = msg->ReadDeltaFloat(nullState.materialParms[5]);
				state->materialParms[6] = msg->ReadDeltaFloat(nullState.materialParms[6]);
				state->materialParms[7] = msg->ReadDeltaFloat(nullState.materialParms[7]);
			}
			else {
				state->flags = msg->ReadDeltaLong(nullState.flags);
				state->origin.x = msg->ReadDeltaFloat(nullState.origin.x);
				state->origin.y = msg->ReadDeltaFloat(nullState.origin.y);
				state->origin.z = msg->ReadDeltaFloat(nullState.origin.z);
				state->angles.pitch = msg->ReadDeltaAngle16(nullState.angles.pitch);
				state->angles.yaw = msg->ReadDeltaAngle16(nullState.angles.yaw);
				state->angles.roll = msg->ReadDeltaAngle16(nullState.angles.roll);
				state->modelIndex = msg->ReadDeltaLong(nullState.modelIndex);
				state->clipModelIndex = msg->ReadDeltaLong(nullState.clipModelIndex);
				state->materialIndex = msg->ReadDeltaLong(nullState.materialIndex);
				state->soundShaderIndex = msg->ReadDeltaLong(nullState.soundShaderIndex);
				state->materialParms[0] = msg->ReadDeltaFloat(nullState.materialParms[0]);
				state->materialParms[1] = msg->ReadDeltaFloat(nullState.materialParms[1]);
				state->materialParms[2] = msg->ReadDeltaFloat(nullState.materialParms[2]);
				state->materialParms[3] = msg->ReadDeltaFloat(nullState.materialParms[3]);
				state->materialParms[4] = msg->ReadDeltaFloat(nullState.materialParms[4]);
				state->materialParms[5] = msg->ReadDeltaFloat(nullState.materialParms[5]);
				state->materialParms[6] = msg->ReadDeltaFloat(nullState.materialParms[6]);
				state->materialParms[7] = msg->ReadDeltaFloat(nullState.materialParms[7]);
				state->solidBox = msg->ReadDeltaLong(nullState.solidBox);
				state->solidContents = msg->ReadDeltaLong(nullState.solidContents);
			}

			count--;
		}

		return offset;
	}

	return -1;
}

/*
 ==================
 CG_ServerCommand
 ==================
*/
static void CG_ServerCommand (void){

	const char	*name;
	int			index;

	name = odCmdSystem->Argv(0);

	if (!Str_ICompare(name, "print"))
		odSystem->Printf("%s", odCmdSystem->Args(1, -1, false));
	else if (!Str_ICompare(name, "cs")){
		index = Str_ToInteger(odCmdSystem->Argv(1));

		if (index < 0 || index >= MAX_CONFIG_STRINGS){
			odSystem->Printf("Bad config string index (%i)\n", index);
			return;
		}

		if (cgs.configStrings[index])
			odMemory->Free(cgs.configStrings[index]);

		cgs.configStrings[index] = odMemory->CopyString(odCmdSystem->Argv(2), TAG_CLIENT);

		// Do something appropriate
		CG_ConfigStringModified(index);
	}
	else
		odSystem->Printf("Unknown server command '%s'\n", name);
}

/*
 ==================
 CG_RunFrame
 ==================
*/
static void CG_RunFrame (int time, const userCmd_t *userCmd){
	cg.time = time;
}

static void CG_AddDefaultLight(){
	renderLight_t	renderLight;
	static int light=0;
	if(light)return;
	renderLight.type = RL_POINT;
	renderLight.lightNum = 0;
	renderLight.origin = odVec3(0,0,0);
	renderLight.center = odVec3(0,0,0);
	renderLight.radius = odVec3(10000,10000,10000);
	renderLight.allowInView = VIEW_ANY;
	renderLight.material=odRenderer->RegisterMaterialLight("_defaultPointLight");
	light = odRenderer->AddRenderLight(cgs.scene, &renderLight);
	odRenderer->UpdateRenderLight(cgs.scene, light, &renderLight);
}
/*
 ==================
 CG_Refresh
 ==================
*/
static void CG_Refresh (void){

	odAngles	shake(M_CRandFloat(), M_CRandFloat(), M_CRandFloat());
	float		f;
	int			area;
	bool		underwater;

	if (!cg.firstSnapshot){
		odRenderer->SetColor(colorTable[COLOR_BLACK]);
		odRenderer->DrawStretchPic(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, cgs.media.whiteMaterial);

		return;
	}

	cg.renderView.x = 0;
	cg.renderView.y = 0;
	cg.renderView.width = SCREEN_WIDTH;
	cg.renderView.height = SCREEN_HEIGHT;

	cg.renderView.fovX = cg_fov->floatValue;

	f = SCREEN_WIDTH / M_Tan(cg.renderView.fovX / 360.0f * M_PI);
	cg.renderView.fovY = M_ATan2(SCREEN_HEIGHT, f) * 360.0f / M_PI;

	cg.renderView.portalSkyOrigin.Clear();

	cg.renderView.time = MS2SEC(cg.time);

	cg.renderView.materialParms[0] = 1.0f;
	cg.renderView.materialParms[1] = 1.0f;
	cg.renderView.materialParms[2] = 1.0f;
	cg.renderView.materialParms[3] = 1.0f;
	cg.renderView.materialParms[4] = 0.0f;
	cg.renderView.materialParms[5] = 0.0f;
	cg.renderView.materialParms[6] = 0.0f;
	cg.renderView.materialParms[7] = 0.0f;

	area = odCollision->PointInArea(cg.renderView.origin);

	underwater = odCollision->Contents(cg.renderView.origin, bounds_zero, MASK_ALL, NULL, vec3_origin, mat3_identity) & MASK_LIQUID;

	if (underwater)
		cg.renderView.materialParms[7] = 1.0f;

	odSound->PlaceListener(cgs.snapshot.state.clientNum, cg.time, cg.renderView.origin, cg.velocity, cg.renderView.axis, underwater, area);

	CG_AddDefaultLight();
	// TODO!!!

	CG_AddEntities();

	// TODO: testing (should this also affect aiming?)
	shake *= odSound->ShakeAmplitudeForListener();
	cg.renderView.axis *= shake.ToMat3();


	odRenderer->RenderScene(cgs.scene, &cg.renderView, true);

	if (cg.testPostProcess){
		odRenderer->SetColor(colorTable[COLOR_WHITE]);
		odRenderer->DrawStretchPic(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, cg.testPostProcessMaterial);
	}

	if (cg_drawCrosshair->integerValue)
		odRenderer->DrawStretchPic(320.0f - 12.0f, 240.0f - 12.0f, 24.0f, 24.0f, 0.0f, 0.0f, 1.0f, 1.0f, odRenderer->RegisterMaterialNoMip(Str_VarArgs("pics/ch%i", cg_drawCrosshair->integerValue)));

	if (cg_showViewPos->integerValue)
		odSystem->Printf("(%s) : %i\n", cg.renderView.origin.ToString(0), M_Ftol(cg.renderViewAngles.yaw));
}

/*
 ==================
 CG_LoadLevel
 ==================
*/

// TODO: this sucks, there should be a better way to do it
static void CG_CompileLights (void){

	entityState_t	*state;
	renderLight_t	renderLight;
	int				handles[MAX_ENTITIES];
	int				i;

	for (i = 0; i < MAX_ENTITIES; i++){
		state = &cgs.baselines[i];

		if (state->type != ET_LIGHT || state->number == 0){
			handles[i] = -1;
			continue;
		}

		if (state->projected)
			renderLight.type = RL_PROJECTED;
		else
			renderLight.type = RL_POINT;

		renderLight.lightNum = state->number;

		renderLight.origin = state->origin;
		renderLight.center = state->center;
		renderLight.axis = state->angles.ToMat3();
		renderLight.radius = state->radius;
		renderLight.fovX = state->fovX;
		renderLight.fovY = state->fovY;
		renderLight.falloffRange = state->falloffRange;
		renderLight.parallel = state->parallel;
		renderLight.noShadows = state->noShadows;
		renderLight.detailLevel = state->detailLevel;
		renderLight.allowInView = VIEW_ANY;
		renderLight.material = cgs.gameMaterials[state->materialIndex];
		memcpy(renderLight.materialParms, state->materialParms, MAX_MATERIAL_PARMS * sizeof(float));

		handles[i] = odRenderer->AddRenderLight(cgs.scene, &renderLight);
	}

	odRenderer->CompileLights(cgs.scene);

	for (i = 0; i < MAX_ENTITIES; i++){
		if (handles[i] == -1)
			continue;

		odRenderer->FreeRenderLight(cgs.scene, handles[i]);
	}
}

static void CG_LoadLevel (void){

	int		time;

	odSystem->Printf("------- Level Loading -------\n");

	time = odSystem->Milliseconds();

	// Load and register all the files for this level
	CG_RegisterMap();
	CG_RegisterModels();
	CG_RegisterMaterials();
	CG_RegisterSoundShaders();

	// Allocate a render scene (we allocate as many handles as possible because
	// we'll use this for rendering the game views)
	cgs.scene = odRenderer->AllocRenderScene(MAX_RENDER_ENTITIES, MAX_RENDER_LIGHTS, MAX_RENDER_PARTICLES, MAX_RENDER_DECALS);

	// Compile lights
//	odRenderer->CompileLights(cgs.scene);
	CG_CompileLights();

	odSystem->Printf("-----------------------------\n");
	odSystem->Printf("Level loaded in %.2f seconds\n", MS2SEC(odSystem->Milliseconds() - time));
}

/*
 ==================
 CG_FreeLevel
 ==================
*/
static void CG_FreeLevel (void){

	int		i;

	cg.testModel = false;
	cg.testModelGun = false;
	cg.testModelEntityHandle = -1;

	cg.testPostProcess = false;

	cg.testSound = false;
	cg.testSoundEmitterHandle = -1;
	cg.testSoundParticleHandle = -1;

	for (i = 0; i < MAX_ENTITIES; i++){
		cg.entities[i].entityHandle = -1;
		cg.entities[i].lightHandle = -1;
		cg.entities[i].emitterHandle = -1;
	}

	// Free the render scene
	if (cgs.scene)
		odRenderer->FreeRenderScene(cgs.scene);
}

/*
 ==================
 CG_Init
 ==================
*/
static void CG_Init (void){

	int		i;

	memset(&cg, 0, sizeof(cg_t));
	memset(&cgs, 0, sizeof(cgs_t));

	// Set the byte swapping variables in a portable manner
	Swap_Init();

	// Set an initial random seed
	M_RandSeed(odSystem->Milliseconds());

	// Register our variables and commands
	cg_footSteps = odCVarSystem->Get("cg_footSteps", "1", CVAR_CHEAT, "Play footstep sound effects");
	cg_noRender = odCVarSystem->Get("cg_noRender", "0", CVAR_CHEAT, "Don't render game views");
	cg_showMiss = odCVarSystem->Get("cg_showMiss", "0", CVAR_CHEAT, "Show prediction misses");
	cg_showDeltaEntities = odCVarSystem->Get("cg_showDeltaEntities", "0", CVAR_CHEAT, "Show delta entities");
	cg_showViewPos = odCVarSystem->Get("cg_showViewPos", "0", CVAR_CHEAT, "Show current view position and angle");
	cg_showMaterial = odCVarSystem->Get("cg_showMaterial", "0", CVAR_CHEAT, "Show material name, flags, and contents under crosshair");
	cg_viewBlend = odCVarSystem->Get("cg_viewBlend", "1", CVAR_ARCHIVE, "Draw blended effects over the game view");
	cg_particles = odCVarSystem->Get("cg_particles", "1", CVAR_ARCHIVE, "Draw particles");
	cg_muzzleFlashes = odCVarSystem->Get("cg_muzzleFlashes", "1", CVAR_ARCHIVE, "Draw muzzle-flashes");
	cg_decals = odCVarSystem->Get("cg_decals", "1", CVAR_ARCHIVE, "Draw decals");
	cg_ejectBrass = odCVarSystem->Get("cg_ejectBrass", "1", CVAR_ARCHIVE, "Draw ejecting brass from weapons");
	cg_blood = odCVarSystem->Get("cg_blood", "1", CVAR_ARCHIVE, "Draw blood");
	cg_shells = odCVarSystem->Get("cg_shells", "1", CVAR_ARCHIVE, "Draw shells");
	cg_drawPlayerShadow = odCVarSystem->Get("cg_drawPlayerShadow", "0", CVAR_ARCHIVE, "Draw player shadow");
	cg_testGunX = odCVarSystem->Get("cg_testGunX", "0", CVAR_CHEAT, "Test gun X offset");
	cg_testGunY = odCVarSystem->Get("cg_testGunY", "0", CVAR_CHEAT, "Test gun Y offset");
	cg_testGunZ = odCVarSystem->Get("cg_testGunZ", "0", CVAR_CHEAT, "Test gun Z offset");
	cg_testModelRotate = odCVarSystem->Get("cg_testModelRotate", "0.0", CVAR_CHEAT, "Test model rotation");
	cg_crosshairX = odCVarSystem->Get("cg_crosshairX", "0", CVAR_ARCHIVE, "Crosshair X offset");
	cg_crosshairY = odCVarSystem->Get("cg_crosshairY", "0", CVAR_ARCHIVE, "Crosshair Y offset");
	cg_crosshairSize = odCVarSystem->Get("cg_crosshairSize", "24", CVAR_ARCHIVE, "Crosshair size");
	cg_crosshairColor = odCVarSystem->Get("cg_crosshairColor", "7", CVAR_ARCHIVE, "Crosshair color index");
	cg_crosshairAlpha = odCVarSystem->Get("cg_crosshairAlpha", "1.0", CVAR_ARCHIVE, "Crosshair translucency level");
	cg_crosshairHealth = odCVarSystem->Get("cg_crosshairHealth", "0", CVAR_ARCHIVE, "Color crosshair based on health");
	cg_crosshairNames = odCVarSystem->Get("cg_crosshairNames", "1", CVAR_ARCHIVE, "Draw player names under crosshair");
	cg_draw2D = odCVarSystem->Get("cg_draw2D", "1", CVAR_ARCHIVE, "Draw 2D elements on screen");
	cg_drawCrosshair = odCVarSystem->Get("cg_drawCrosshair", "0", CVAR_ARCHIVE, "Draw crosshair");
	cg_drawLagometer = odCVarSystem->Get("cg_drawLagometer", "0", CVAR_ARCHIVE, "Draw lagometer");
	cg_drawDisconnected = odCVarSystem->Get("cg_drawDisconnected", "1", CVAR_ARCHIVE, "Draw connection interrupted warnings");
	cg_spectator = odCVarSystem->Get("cg_spectator", "0", CVAR_USERINFO, "Spectator mode");
	cg_autoSwitch = odCVarSystem->Get("cg_autoSwitch", "0", CVAR_ARCHIVE | CVAR_USERINFO, "Auto-switch weapons");
	cg_autoReload = odCVarSystem->Get("cg_autoReload", "0", CVAR_ARCHIVE | CVAR_USERINFO, "Auto-reload weapons");
	cg_drawGun = odCVarSystem->Get("cg_drawGun", "1", CVAR_ARCHIVE | CVAR_USERINFO, "Draw gun");
	cg_fov = odCVarSystem->Get("cg_fov", "90", CVAR_ARCHIVE | CVAR_USERINFO, "Field of view");
	cg_predict = odCVarSystem->Get("cg_predict", "1", CVAR_ARCHIVE | CVAR_USERINFO, "Predict player movement");

	CG_AddGameCommands();

	// Get the GL and AL configurations
	odRenderer->GetGLConfig(&cgs.glConfig);
	odSound->GetALConfig(&cgs.alConfig);

	// Clear handles
	cg.testModelEntityHandle = -1;
	cg.testSoundEmitterHandle = -1;
	cg.testSoundParticleHandle = -1;

	for (i = 0; i < MAX_ENTITIES; i++){
		cg.entities[i].entityHandle = -1;
		cg.entities[i].lightHandle = -1;
		cg.entities[i].emitterHandle = -1;
	}

	// Set up stuff
	cgi->SetDeltaAngles(cg.deltaAngles);
	cgi->SetSensitivity(1.0f);

	cgs.baselines = (entityState_t *)odMemory->ClearedAlloc(MAX_ENTITIES * sizeof(entityState_t), TAG_CLIENT);
	cgs.parseEntities = (entityState_t *)odMemory->Alloc(MAX_PARSE_ENTITIES * sizeof(entityState_t), TAG_CLIENT);
}

/*
 ==================
 CG_Shutdown
 ==================
*/
static void CG_Shutdown (void){

	// Free the level if loaded
	CG_FreeLevel();

	// Unregister our commands
	CG_RemoveGameCommands();

	// Free all memory
	odMemory->TagFree(TAG_CLIENT);
}


// ============================================================================


/*
 ==================
 GetCGameAPI

 This is the only function exported by the game module
 ==================
*/
cgExport_t *GetCGameAPI (cgImport_t *import){

	cgi = import;

	odMemory			= &import->odMemory;
	odFileSystem		= &import->odFileSystem;
	odCmdSystem			= &import->odCmdSystem;
	odCVarSystem		= &import->odCVarSystem;
	odParser			= &import->odParser;
	odSystem			= &import->odSystem;
	odCollision			= &import->odCollision;
	odRenderer			= &import->odRenderer;
	odSound				= &import->odSound;
	odEditor			= &import->odEditor;

	// Set the exported functions
	cge.apiVersion		= CGAME_API_VERSION;

	cge.gameProtocol	= GAME_PROTOCOL_VERSION;

	cge.WriteGameState	= CG_WriteGameState;
	cge.ParseGameState	= CG_ParseGameState;
	cge.ParseSnapshot	= CG_ParseSnapshot;
	cge.ServerCommand	= CG_ServerCommand;
	cge.RunFrame		= CG_RunFrame;
	cge.Refresh			= CG_Refresh;
	cge.LoadLevel		= CG_LoadLevel;
	cge.FreeLevel		= CG_FreeLevel;
	cge.Init			= CG_Init;
	cge.Shutdown		= CG_Shutdown;

	return &cge;
}
